/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @author Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 * @version 1.00, 24/06/2013
 * Native implementation of the measure library for CoNet (http://psbweb05.psb.ugent.be/conet/)
 */

#include "renormalizer.h"

void free_renormalizer_config(JNIEnv *env, DefaultRenormalizerConfig * config){
	if(config == NULL)return;
	int i;

	if(config->sum != NULL){
		for(i=0;i<config->groupCount;i++)
			free(config->sum[i]);
		free(config->sum);
		config->sum = NULL;
	}
	if(config->groups != NULL){
		free(config->groups);
		config->groups = NULL;
		//TODO DeleteWeakGlobalRef(env,(jweak)config->groups);
		config->groups = NULL;
	}
	if(config->features != NULL){
		//TODO DeleteWeakGlobalRef(env,(jweak)config->features);
		config->features = NULL;
	}
	if(config->specificNormalizer != NULL){
		//for(i=0;i<config->groupCount;i++)
		//	free(config->specificNormalizer[i]);
		free(config->specificNormalizer);
		config->specificNormalizer = NULL;
		free(config->specificSize);
		config->specificSize = NULL;
	}
	free(config);
 }

DefaultRenormalizerConfig * defaultRenormalizer(JNIEnv * env,jobject renormalizer,jdouble * data[],const jsize N,const jsize M){
	if(renormalizer == NULL)return NULL;
	DefaultRenormalizerConfig * config = (DefaultRenormalizerConfig*) calloc(1,sizeof(DefaultRenormalizerConfig));
	config->M = M;
	const jclass renormClass = (*env)->FindClass(env, DEFAULT_RENORMALIZER_CLASS);
	//we only implement the native code of DEFAULT_RENORMALIZER_CLASS
	if(!java_equals(env,(*env)->GetObjectClass(env,renormalizer),renormClass)) return NULL;
	config->groupCount = (*env)->GetIntField(env,renormalizer,(*env)->GetFieldID(env,renormClass,"groupCount","I"));

	if(config->groupCount > 1){
		config->groups				= (*env)->GetDirectBufferAddress(env,(*env)->CallObjectMethod(env,renormalizer,(*env)->GetMethodID(env, renormClass, "jni_groupsHelper","()Ljava/nio/IntBuffer;")));
		// TODO ? config->groups = NewWeakGlobalRef(env,config->groups);
	}else{
		config->groups				= NULL;//with calloc, all bytes are 0 => NULL so it is not useful
	}

	if(property_enabled(env,renormClass,renormalizer,"isUsingCustomNormalizer")){
		config->specificNormalizer	= (jdouble **)calloc(config->groupCount,sizeof(jdouble *));
		config->specificSize		= (jsize *)calloc(config->groupCount,sizeof(jsize));
		// TODO ? config->specificNormalizer/specificSize = NewWeakGlobalRef(env,config->groups);
	}else{
		config->specificNormalizer	= NULL;//with calloc, all bytes are 0 => NULL so it is not useful
		config->specificSize		= NULL;//with calloc, all bytes are 0 => NULL so it is not useful
	}


	if(property_enabled(env,renormClass,renormalizer,"isUsingFeatures")){
		config->features = (*env)->GetDirectBufferAddress(env,(*env)->CallObjectMethod(env,renormalizer,(*env)->GetMethodID(env, renormClass, "jni_featuresHelper","()Ljava/nio/IntBuffer;")));
		// TODO ? config->features = NewWeakGlobalRef(env,config->features);
	}else{
		config->features = NULL;//with calloc, all bytes are 0 => NULL so it is not useful
	}
	config->pairShuffle = property_enabled(env,renormClass,renormalizer,"shufflePair");
	int i,j;
	// compute the sums
	config->sum = (jdouble **)malloc(sizeof(jdouble *) * (config->groupCount));
	{
		jobjectArray buff = (*env)->CallObjectMethod(env,renormalizer,(*env)->GetMethodID(env, renormClass, "jni_specificHelper","()[Ljava/nio/DoubleBuffer;"));
		for(i=0;i<config->groupCount;i++){
			config->sum[i]					= (jdouble *)calloc(M,sizeof(jdouble));
		}
		if(config->specificNormalizer != NULL){
			for(i=0;i<config->groupCount;i++){
				config->specificNormalizer[i]	= (*env)->GetDirectBufferAddress(env,(*env)->GetObjectArrayElement(env,buff,i));// TODO weak global ref and free in measurer at end (see 2 lines after)
				config->specificSize[i]			= (jsize) *(config->specificNormalizer[i]);
				config->specificNormalizer[i]++;// TODO weak global ref and free in measurer at end
			}
		}
	}
	if(config->groupCount > 1){
		if(config->features != NULL){
			int pos = 0;
			for(i=0;i<N;i++)
				if(i<config->features[pos]) sumCol(data[i],config->sum[config->groups[i]],M);
				else pos++;

			mergeRenormilizer(config);
			
			// normalize
			pos = 0;
			for(i=0;i<N;i++)
				if(i<config->features[pos]){
					for(j=0;j<M;j++)
						data[i][j]/=config->sum[config->groups[i]][j];
				}else pos++;
		}else{
			for(i=0;i<N;i++)
				sumCol(data[i],config->sum[config->groups[i]],M);

			mergeRenormilizer(config);

			// normalize
			for(i=0;i<N;i++)
				for(j=0;j<M;j++)
					data[i][j]/=config->sum[config->groups[i]][j];
		}

	}else{
		if(config->features != NULL){
			int pos = 0;
			for(i=0;i<N;i++)
				if(i<config->features[pos]) sumCol(data[i],config->sum[0],M);
				else pos++;

			mergeRenormilizer(config);

			// normalize
			pos = 0;
			for(i=0;i<N;i++)
				if(i<config->features[pos]){
					for(j=0;j<M;j++)
						data[i][j]/=config->sum[0][j];
				}else pos++;
		}else{
			for(i=0;i<N;i++)
				sumCol(data[i],config->sum[0],M);
			mergeRenormilizer(config);
			// normalize
			for(i=0;i<N;i++)
				for(j=0;j<M;j++)
					data[i][j]/=config->sum[0][j];
		}
	}
	if(config->pairShuffle){

		for(i=0;i<config->groupCount;i++)
			for(j=0;j<M;j++)
				config->sum[i][j] = 1.0;
		mergeRenormilizer(config);

		return config;
	}

	#if FORCE_FREE == 1
		free_renormalizer_config(env,config);
	#endif

	return NULL;
} 

void inline mergeRenormilizer(DefaultRenormalizerConfig * config){
	if(config->specificSize != NULL){
		int group,col;
		for(group=0;group<config->groupCount;group++){
			for(col=0;col<config->specificSize[group];col++)
				if(config->specificNormalizer[group][col] != SUM_NORMILIZER)
					config->sum[group][col] = config->specificNormalizer[group][col];
		}
	}
}

void inline sumCol(jdouble * row,jdouble * sum,jsize M){
	{
	int col;
	for(col=0;col<M;col++)
		if(! isnan(row[col]))
			sum[col] += row[col];
	}
}

void inline diffCol(jdouble * row,jdouble * sum,jsize M){
	{
	int col;
	for(col=0;col<M;col++)
		if(! isnan(row[col]))
			sum[col] -= row[col];
	}
}
void simulateRenormalization(DefaultRenormalizerConfig * config,jdouble * shuffledFirst,jdouble * shuffledSecond,const int first,const int second){
	jdouble * ptr[]	= {shuffledFirst,shuffledSecond};
	int groupPtr[]	= {0,0};
	int from,to;
	{
		if(config->groupCount > 1){
			groupPtr[0] = config->groups[first];
			groupPtr[1] = config->groups[second];
		}
		const int processFirst	= !(config->features != NULL && features_contains(config->features,first));
		const int processSecond	= !(config->features != NULL && features_contains(config->features,second));
		if(processFirst){
			if(processSecond){
				from=0;
				to=2;
			}else{
				from=0;
				to=1;
			}
		}else if(processSecond){
			from=1;
			to=2;
		}else{
			return;//do not process
		}
	}

	updateRenormalization(config,shuffledFirst,first,0);
	updateRenormalization(config,shuffledSecond,second,0);

	int col,vect;
	jdouble tmp[2];

	for(col=0;col<config->M;col++){
		for(vect=from;vect<to;vect++){
			tmp[vect]		=  ptr[vect][col];
			ptr[vect][col]	/= config->sum[groupPtr[vect]][col];
		}
		for(vect=from;vect<to;vect++){
			if(! isnan(tmp[vect]))
				config->sum[groupPtr[vect]][col] -= tmp[vect];
		}
	}

	mergeRenormilizer(config);
}


int features_contains(jint* features, const int row){
	int pos;
	while(features[pos] < row) pos++;
	return features[pos] == row;
}

void updateRenormalization(DefaultRenormalizerConfig * config,double * vect,const int row, const int ignore){
	if(config->features != NULL && features_contains(config->features,row)){
		// no need to take into account because this element must be ignored
		return;
	}
	if(config->groupCount > 1){
		if(ignore) diffCol(vect,config->sum[config->groups[row]],config->M);
		else sumCol(vect,config->sum[config->groups[row]],config->M);
	}else{
		if(ignore) diffCol(vect,config->sum[0],config->M);
		else sumCol(vect,config->sum[0],config->M);
	}
	mergeRenormilizer(config);
}
